/*
 * Copyright 2000 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "WtContextEvents.h"
#include "WtTableCmds.h"
#include "WtTable.h"
#include "WtTableUtil.h"
#include "WtUtil.h"
#include "WtBasicCmds.h"
#include "WtWebErrors.h"

/* Eval event handler scripts */

int WtEvalEventHandlers(Tcl_Obj **tablePtr, const char *name,
    Tcl_Interp *interp, WtContext *w)
{
  int ok = 1, i, len, isNewHandler, isNewList, isNewTable, status,
    eventStatus = WT_STATUS_OK, errorSetId = -1;
  Tcl_Obj *list, *handler, *script, *str;
  char errMsg[512];

  if (!WtStartErrors(w, interp, &errorSetId)) {
    return 0;
  }

  list = WtTableGetObjFromStr(*tablePtr, name, NULL);

  if (list) {
    if (Tcl_ListObjLength(interp, list, &len) != TCL_OK) {
      ok = 0;
    } else if (len) {
      for (i = 0; i < len; i++) {
        if (Tcl_ListObjIndex(interp, list, i, &handler) != TCL_OK) {
          ok = 0;
          WtAddInterpError(interp, w);
          break;
        }

        if (!WtConvertToTable(handler, interp)) {
          ok = 0;
          WtAddInterpError(interp, w);
          break;
        }

        script = WtTableGetObjFromStr(handler, "script", NULL);

        if (!script) {
          ok = 0;
          str = WtNewString("Invalid object.");
          Tcl_IncrRefCount(str);
          WtAddError(NULL, str, interp, w);
          break;
        }

        WtLog(HERE, APLOG_DEBUG | APLOG_NOERRNO, w,
          "WtEvalEventHandlers: Handling \"%s\" event. Evaluating: "
          "\"%s\".", name, WtToString(script));

        if (Tcl_EvalObj(interp, script) != TCL_OK) {
          status = WT_STATUS_ERROR;
          eventStatus = WT_STATUS_ERROR;
          sprintf(errMsg, "\n    (\"%s\" script #%d)",
            name, i + 1);
          Tcl_AddObjErrorInfo(interp, errMsg, -1);
          WtAddEvalError(interp, w);
        } else {
          status = WT_STATUS_OK;
        }

        WtLog(HERE, APLOG_DEBUG | APLOG_NOERRNO, w,
          "WtEvalEventHandlers: Script status: %d.", status);

        /* Update the status */

        isNewHandler = WtCopyOnWrite(&handler);
        WtTableSetStrToInt(handler, "status", status);

        isNewList = WtCopyOnWrite(&list);
        Tcl_IncrRefCount(handler);

        if (Tcl_ListObjReplace(interp, list, i, 1, 1, &handler) != TCL_OK) {
          ok = 0;
          WtAddInterpError(interp, w);
        } else {
          isNewTable = WtCopyOnWrite(tablePtr);
          WtTableSetStrToObj(*tablePtr, name, list);
        }

        Tcl_DecrRefCount(handler);

        if (isNewHandler) {
          Tcl_DecrRefCount(handler);
        }

        if (isNewList) {
          Tcl_DecrRefCount(list);
        }

        if (!ok) {
          break;
        }

        list = WtTableGetObjFromStr(*tablePtr, name, NULL);
      }
    }
  }

  if (eventStatus != WT_STATUS_OK) {
    ok = 0;
  }

  if (errorSetId != -1) {
    if (WtStopErrors(w, interp, errorSetId, 1) == -1) {
      ok = 0;
    }
  }

  return ok;
}

/* events command */

int WtContextEventsCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  WtContext *w = WtGetAssocContext(interp);
  char *subCmd;

  if (objc < 2) {
    WtContextEventsUsage(interp, objv[0]);
  } else {
    subCmd = Tcl_GetString(objv[1]);

    if (!strcmp(subCmd, "items")) {
      ret = WtCtxEvtItemsCmd(clientData, interp, objc, objv);
    } else if (!strcmp(subCmd, "add")) {
      ret = WtCtxEvtAddCmd(clientData, interp, objc, objv);
    } else if (!strcmp(subCmd, "remove")) {
      ret = WtCtxEvtRemoveCmd(clientData, interp, objc, objv);
    } else if (!strcmp(subCmd, "fire")) {
      ret = WtCtxEvtFireCmd(clientData, interp, objc, objv);
    } else if (!strcmp(subCmd, "handlers")) {
      ret = WtCtxEvtHandlersCmd(clientData, interp, objc, objv);
    } else if (!strcmp(subCmd, "addHandler")) {
      ret = WtCtxEvtAddHandlerCmd(clientData, interp, objc, objv);
    } else if (!strcmp(subCmd, "removeHandler")) {
      ret = WtCtxEvtRemoveHandlerCmd(clientData, interp, objc, objv);
    } else {
      WtContextEventsUsage(interp, objv[0]);
    }
  }

  return ret;
}

/* items command */

int WtCtxEvtItemsCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  WtContext *w = WtGetAssocContext(interp);

  if (objc == 2) {
    Tcl_SetObjResult(interp, w->web->ctxEventHandlers);
    ret = TCL_OK;
  } else if (objc == 3) {
    WtSetObj(&w->web->ctxEventHandlers, objv[2]);
    ret = TCL_OK;
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " ?newItems?", NULL);
  }

  return ret;
}

/* add command */

int WtCtxEvtAddCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  Tcl_Obj *list;
  WtContext *w = WtGetAssocContext(interp);

  if (objc == 3) {
    if (WtOwnTable(&w->web->ctxEventHandlers, interp)) {
      list = Tcl_NewListObj(0, NULL);
      WtTableSet(w->web->ctxEventHandlers, objv[2], list);
      ret = TCL_OK;
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " eventName", NULL);
  }

  return ret;
}

/* remove command */

int WtCtxEvtRemoveCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  WtContext *w = WtGetAssocContext(interp);

  if (objc == 3) {
    if (WtOwnTable(&w->web->ctxEventHandlers, interp)) {
      WtTableRemove(w->web->ctxEventHandlers, objv[2]);
      ret = TCL_OK;
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " eventName", NULL);
  }

  return ret;
}

/* fire command */

int WtCtxEvtFireCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc == 3) {
    Tcl_AppendResult(interp, "Not implemented.", NULL);
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " eventName", NULL);
  }

  return ret;
}

/* handlers command */

int WtCtxEvtHandlersCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  Tcl_Obj *list;
  WtContext *w = WtGetAssocContext(interp);

  if (objc == 4) {
    if (WtOwnTable(&w->web->ctxEventHandlers, interp)) {
      WtTableSet(w->web->ctxEventHandlers, objv[2], objv[3]);
      ret = TCL_OK;
    }
  } else if (objc == 3) {
    list = WtTableGet(w->web->ctxEventHandlers, objv[2]);
    Tcl_SetObjResult(interp, list ? list : Tcl_NewListObj(0, NULL));
    ret = TCL_OK;
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " eventName ?handlers?", NULL);
  }

  return ret;
}

/* addHandler command */

int WtCtxEvtAddHandlerCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  Tcl_Obj *hook;
  WtContext *w = WtGetAssocContext(interp);

  if (objc == 4) {
    if (WtOwnTable(&w->web->ctxEventHandlers, interp)) {
      hook = WtNewTableObj();
      Tcl_IncrRefCount(hook);
      WtTableSetStrToObj(hook, "script", objv[3]);
      WtTableSetStrToInt(hook, "status", WT_STATUS_NONE);

      if (WtTableAppendToList(w->web->ctxEventHandlers,
          objv[2], hook, NULL, interp)) {
        ret = TCL_OK;
      }

      Tcl_DecrRefCount(hook);
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " eventName handler", NULL);
  }

  return ret;
}

/* removeHandler command */

int WtCtxEvtRemoveHandlerCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR, len, i;
  Tcl_Obj *list, *elem;
  WtContext *w = WtGetAssocContext(interp);

  if (objc == 4) {
    if (WtOwnTable(&w->web->ctxEventHandlers, interp)) {
      list = WtTableGet(w->web->ctxEventHandlers, objv[2]);
      if (Tcl_ListObjLength(interp, list, &len) == TCL_OK) {
        ret = TCL_OK;
        for (i = len; i >= 0; --i) {
          if (Tcl_ListObjIndex(interp, list, i, &elem) != TCL_OK) {
            ret = TCL_ERROR;
            break;
          }
          if (!strcmp(WtToString(elem), WtToString(objv[3]))) {
            if (Tcl_ListObjReplace(interp, list, i, 1, 0, NULL) != TCL_OK) {
              ret = TCL_ERROR;
              break;
            }
          }
        }
      }
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " eventName handler", NULL);
  }

  return ret;
}

void WtContextEventsUsage(Tcl_Interp *interp, Tcl_Obj *cmd)
{
  char *cmdStr = WtToString(cmd);

  Tcl_AppendResult(interp,
    wtBadUsagePrefix2,
    cmdStr, " items\n",
    cmdStr, " add eventName\n",
    cmdStr, " remove eventName\n",
    cmdStr, " fire eventName\n",
    cmdStr, " handlers eventName ?handlers?\n",
    cmdStr, " addHandler eventName handler\n",
    cmdStr, " removeHandler eventName handler\n",
    NULL);
}

/* Init event handler scripts */

int WtInitCtxEventHandlers(WtContext *w, Tcl_Interp *interp)
{
  int ok = 1, len, i;
  Tcl_HashTable *hashTable;
  Tcl_HashEntry *ent;
  Tcl_HashSearch search;
  Tcl_Obj *key, *list, *hooks, *hook, *item;
  WtWebContext *web = w->web;

  if (!web) {
    return 0;
  }

  if (!web->ctxEventHandlers) {
    WtSetObj(&web->ctxEventHandlers, WtNewTableObj());
  }

  if (!WtConvertToTable(web->taskSettings, interp)) {
    WtAddInterpError(interp, w);
    ok = 0;
  } else {
    hooks = WtTableGetObjFromStr(web->taskSettings,
      "server.ctxEventHandlers", NULL);

    if (hooks) {
      hashTable = WtGetTableMap(hooks);
      ent = Tcl_FirstHashEntry(hashTable, &search);

      while (ent) {
        key = (Tcl_Obj *)Tcl_GetHashKey(hashTable, ent);
        list = (Tcl_Obj *)Tcl_GetHashValue(ent);

        if (Tcl_ListObjLength(interp, list, &len) != TCL_OK) {
          WtAddInterpError(interp, w);
          ok = 0;
          break;
        }

        for (i = 0; i < len; i++) {
          if (Tcl_ListObjIndex(interp, list, i, &item) != TCL_OK) {
            WtAddInterpError(interp, w);
            ok = 0;
            break;
          }

          hook = WtNewTableObj();

          WtTableSetStrToObj(hook, "script",
            WtTableGetObjFromStr(item, "script", NULL));

          WtTableSetStrToInt(hook, "status", WT_STATUS_NONE);

          if (!WtTableAppendToList(web->ctxEventHandlers,
              key, hook, NULL, interp)) {
            WtAddInterpError(interp, w);
            ok = 0;
            break;
          }
        }

        if (!ok) {
          break;
        }

        ent = Tcl_NextHashEntry(&search);
      }
    }
  }

  return ok;
}

int WtAtEndCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  Tcl_Obj *key, *hook;
  WtContext *w = WtGetAssocContext(interp);

  if (objc == 2) {
    if (WtOwnTable(&w->web->ctxEventHandlers, interp)) {
      key = WtNewString("endRequest");
      Tcl_IncrRefCount(key);

      hook = WtNewTableObj();
      Tcl_IncrRefCount(hook);
      WtTableSetStrToObj(hook, "script", objv[1]);
      WtTableSetStrToInt(hook, "status", WT_STATUS_NONE);

      if (WtTableAppendToList(w->web->ctxEventHandlers,
          key, hook, NULL, interp)) {
        ret = TCL_OK;
      }

      Tcl_DecrRefCount(hook);
      Tcl_DecrRefCount(key);
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " script", NULL);
  }

  return ret;
}
